#ifndef __EPSILON__STRING_H__
#define __EPSILON__STRING_H__

#include "strings.h"
#include "debug.h"

/// TODO: Create an Array class for Epsilon.
#include <vector>
#define eTArray std::vector

/* Avoid depricated warnings for some string functions in Visual C++...

    I have opted not to fix the problem using _CRT_NONSTDC_NO_DEPRECATE,
      because it could hide other, more useful warnings.
*/
#ifdef _MSC_VER

# undef  strdup
# undef  stricmp

# define strdup _strdup
# define stricmp _stricmp

#endif

/// The type used to store the size may be overriden to save space.
///
///  * Keep in mind that the size type is never checked when assigning
///    the string its data, and the size type may be too small to store
///    the length of longer strings.
///
///   @ Limited to strings <= 65 Kb in length (by default).
template <typename charT, typename sizeT = unsigned short> class eTString;

class eStringW;
class eStringA;

//typedef eTString<wchar_t> eStringW; ///< Wide String
//typedef eTString<char>    eStringA; ///< ANSI String
typedef eTString<TCHAR>   eString;  ///< Default String
                                    ///  Wide or ANSI depending on build config

template <typename charT, typename sizeT>
class eTString {
public:
  eTString (void)                  : m_len (0) { };
//  eTString (const charT* szString) : m_len (0) {
//    Set (szString);
//  }

  /// Special constructor that will delete the passed buffer... be careful.
/*
  eTString (charT** pszString) : m_len (0) {
    if (! pszString)
      return;

    Set (*pszString);

    delete [] *pszString;
    pszString = NULL;
  }
 */

  virtual ~eTString (void) {
    m_str.clear ();
    m_len = 0;
  }

  eStringA ANSI (void) const;
  eStringW Wide (void) const;

  void Set (const charT* szString);
  int  Format (const charT* szFormat, ...);

  const charT* CString (void) const {
    return &m_str [0];
  }

  unsigned int GetLength (void) const {
    return (unsigned int)m_len;
  }

protected:
  sizeT           m_len;
  eTArray<charT>  m_str;
};

class eStringW : public eTString <wchar_t>
{
public:
				eStringW (void)                    : eTString <wchar_t> () { };
				eStringW (const wchar_t* szString) : eTString <wchar_t> () { Set (szString); };

//				eStringW (wchar_t** pszString)     : eTString <wchar_t> (pszString) { };

				eStringA ANSI (void) const;
				eStringW Wide (void) const;

				void Set    (const wchar_t* szString);
				int  Format (const wchar_t* szFormat, ...);

protected:
};

class eStringA : public eTString<char>
{
public:
				eStringA (void)                 : eTString <char> () { };
				eStringA (const char* szString) : eTString <char> () { Set (szString); };

//				eStringA (char** pszString)     : eTString <char> (pszString) { };

				eStringA ANSI (void) const;
				eStringW Wide (void) const;

				void Set    (const char* szString);
				int  Format (const char* szFormat, ...);

protected:
};


inline
eStringW
eStringW::Wide (void) const
{
  return *this;
}

inline
eStringA
eStringW::ANSI (void) const
{
	/*
  char* szString = new char [m_len + 1];
  char* pszStr   = szString;

  for (size_t i = 0; i < m_len; i++) {
    *pszStr++ = *(char *)&m_str [i];
  }

  szString [m_len] = '\0';

  return eStringA (&szString);
  */

	return eStringA (NULL);
}

inline
void
eStringW::Set (const wchar_t* szString)
{
  Assert (szString != NULL, _T ("Invalid string"));

  if (! szString)
    return;

  size_t iStrLen = wcslen (szString);

  if (m_str.size () < (iStrLen + 1))
    m_str.resize (iStrLen + 1);

  wcsncpy (&m_str [0], szString, iStrLen);

  m_len         = iStrLen;
  m_str [m_len] = L'\0';
}

inline
int
eStringW::Format (const wchar_t* szFormat, ...)
{
  va_list argv;

  Assert (szFormat != NULL, _T ("Invalid format string"));

  if (szFormat == NULL)
    return 0;

  int size = 32;
  m_str.resize (size);

  int len = 0;

  while (true) {
    va_start (argv, szFormat);
      len = vswprintf (&m_str [0], size, szFormat, argv);
    va_end (argv);

    if (len > -1 && len < size) {
      m_str.resize (len + 1);
      m_str [len] = L'\0';
      return (m_len = len);
    }

    if (len > -1)
      size = len + 1;
    else
      size *= 2;

    m_str.resize (size);
  }
}


inline
eStringW
eStringA::Wide (void) const
{
	/*
  wchar_t* szString = new wchar_t [m_len + 1];
  char*    pszStr   = (char *)szString;

  for (size_t i = 0; i < m_len; i++) {
    *pszStr++ = m_str [i];
    *pszStr++ = 0x00;
  }

  szString [m_len] = _T ('\0');

  return eStringW (&szString);
  	*/
  return eStringW (NULL);
}

inline
eStringA
eStringA::ANSI (void) const
{
  return *this;
}

inline
void
eStringA::Set (const char* szString)
{
  Assert (szString != NULL, _T ("Invalid string"));

  if (! szString)
    return;

  size_t iStrLen = strlen (szString);

  if (m_str.size () < (iStrLen + 1))
    m_str.resize (iStrLen + 1);

  strncpy (&m_str [0], szString, iStrLen);

  m_len         = iStrLen;
  m_str [m_len] = '\0';
}

inline
int
eStringA::Format (const char* szFormat, ...)
{
  va_list argv;

  Assert (szFormat != NULL, _T ("Invalid format string"));

  if (szFormat == NULL)
    return 0;

  int size = 32;
  m_str.resize (size);

  int len = 0;

  while (true) {
    va_start (argv, szFormat);
      len = vsnprintf (&m_str [0], size, szFormat, argv);
    va_end (argv);

    if (len > -1 && len < size) {
      m_str.resize (len + 1);
      m_str [len] = '\0';
      return (m_len = len);
    }

    if (len > -1)
      size = len + 1;
    else
      size *= 2;

    m_str.resize (size);
  }
}

template <class stringT, typename charT>
void
eTString_Set (stringT* pString, const charT* szString)
{
	printf ("Test\n");
	pString->Set (szString);
}

template <typename charT, typename sizeT>
void
eTString <charT, sizeT>::Set (const charT* szString)
{
	eTString_Set <charT> (this, szString);
}


///
/// The following eScopedString classes are useful for passing
///   strings allocated on the heap, without having to remember to
///     free them...
///
///   More importantly, you can use them to duplicate a string
///     created on the stack, so that it is safe to use said string
///       as a return value. 
///
///     -- To Do So:  Use eScopedString (<Stack_String>, true);
///

/**
 * Some methods return a newly allocated string (using heap storage),
 *   if you construct an eScopedString object from one of these C-style
 *     strings, you can rest assured that its memory will be freed
 *       when this object goes out of scope.
**/
class eScopedStringConst {
public:
  /* If alloc is true, then allocate additional storage for the
       string... */
  eScopedStringConst (const char* str, bool alloc = false) {
    if (alloc)
      string_ = (const char *)strdup (str);
    else 
      string_ = str;
  }

  ~eScopedStringConst (void) {
    if (string_ != NULL) {
      delete [] string_;
      string_ = NULL;
    }
  }

  operator const char* (void) {
    if (string_ == NULL)
      return "(null)";

    return string_;
  }

private:
  const char* string_;
};

/**
 * Some methods return a newly allocated string (using heap storage),
 *   if you construct an eScopedString object from one of these C-style
 *     strings, you can rest assured that its memory will be freed
 *       when this object goes out of scope.
**/
class eScopedString {
public:
  /* If alloc is true, then allocate additional storage for the
       string... */
  eScopedString (char* str, bool alloc = false) {
    if (alloc)
      string_ = strdup (str);
    else
      string_ = str;
  }

  /* If alloc is true, then allocate additional storage for the
       string... */
  eScopedString (const char* str, bool alloc = false) {
    if (alloc)
      string_ = strdup (str);
    else
      string_ = NULL;

    /* It is an error to not use alloc when passing a const char*
         to a non-const eScopedString! */
  }

  ~eScopedString (void) {
    if (string_ != NULL) {
      delete [] string_;
      string_ = NULL;
    }
  }

  operator char* (void) {
    if (string_ == NULL)
      return (char *)"(null)";

    return string_;
  }

private:
  char* string_;
};


#endif __EPSILON__STRING_H__
